﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;

using ABCat.Core;
using ABCat.Core.Plugins.Catalog.FilteringLogics;
using ABCat.Core.Plugins.Catalog.GrouppingLogics;
using ABCat.Core.Plugins.DataProviders;
using ABCat.Core.Plugins.DataSets;
using ABCat.Core.Plugins.Sites;
using ABCat.Core.Plugins.UI;
using ABCat.Shared;
using ABCat.Shared.Properties;

namespace ABCat.UI.WPF.Models
{
	public sealed class ABCatViewModel : INotifyPropertyChanged
	{
		#region + Events +

		public event PropertyChangedEventHandler PropertyChanged;

		#endregion
		#region + Fields +

		public readonly Context Context;

		private IBrowserWindowPlugin _browserWindowPlugin;
		private IDbContainer _dbContainer4UI;
		private IFilteringLogicPlugin _filter;
		private CancellationTokenSource _getRecordsCancellationTokenSource;
		private GrouppingLogicViewModel _grouppingLogicModel;
		private IGrouppingLogicPlugin _grouppingLogicPlugin;
		private IRecordsListPlugin _recordsListUc;
		private readonly DelegateCommand _hideSelectedRecordsCommand;
		private readonly DelegateCommand _refreshCommand;
		private readonly DelegateCommand _setReplacementCommand;
		private readonly DelegateCommand _showCachedInBrowserCommand;
		private readonly DelegateCommand _configCommand;

		#endregion
		#region + Properties +

		public IBrowserWindowPlugin BrowserWindowPlugin
		{
			get
			{
				if (_browserWindowPlugin == null)
				{
					_browserWindowPlugin = Context.PluginsFactory.GetPlugins<IBrowserWindowPlugin>().First().CreateInstance<IBrowserWindowPlugin>(Context);
					_browserWindowPlugin.WindowClosed += BrowserWindowPluginWindowPluginClosed;
				}
				return _browserWindowPlugin;
			}
		}

		public IFilteringLogicPlugin Filter
		{
			get { return _filter; }
			private set
			{
				if (Equals(value, _filter)) return;
				_filter = value;
				OnPropertyChanged();
			}
		}

		public GrouppingLogicViewModel GrouppingLogicModel
		{
			get { return _grouppingLogicModel; }
			set
			{
				if (Equals(value, _grouppingLogicModel)) return;
				_grouppingLogicModel = value;
				OnPropertyChanged();
			}
		}

		[UsedImplicitly]
		public DelegateCommand HideSelectedRecordsCommand
		{
			get
			{
				return _hideSelectedRecordsCommand;
			}
		}


		public NormalizationSettingsEditorViewModel NormalizationSettingsEditorModel		{get; private set;}
		public IRecordsListPlugin RecordsListUc
		{
			get { return _recordsListUc; }
			private set
			{
				if (Equals(value, _recordsListUc)) return;
				if (_recordsListUc != null) _recordsListUc.ItemDoubleClick -= _recordsListUc_ItemDoubleClick;
				_recordsListUc = value;
				if (_recordsListUc != null) _recordsListUc.ItemDoubleClick += _recordsListUc_ItemDoubleClick;
				OnPropertyChanged();
				OnPropertyChanged("SelectedItems");
			}
		}

		public RecordTargetDownloaderViewModel RecordTargetDownloaderModel		{get; private set;}
		[UsedImplicitly]
		public DelegateCommand RefreshCommand
		{
			get
			{
				return _refreshCommand;
			}
		}

		public IEnumerable<IAudioBook> SelectedItems
		{
			get { return RecordsListUc.SelectedItems; }
		}

		[UsedImplicitly]
		public DelegateCommand SetReplacementCommand
		{
			get
			{
				return _setReplacementCommand;
			}
		}

		[UsedImplicitly]
		public DelegateCommand ShowCachedInBrowserCommand
		{
			get
			{
				return _showCachedInBrowserCommand;
			}
		}

		public SiteParserViewModel SiteParserModel		{get; private set;}
		public StatusBarStateViewModel StatusBarStateModel		{get; private set;}

		public DelegateCommand ConfigCommand
		{
			get { return _configCommand; }
		}

		#endregion
		#region + Ctor +

		public ABCatViewModel(Context context)
		{
			Context = context;
			RecordsListUc = Context.PluginsFactory.GetPlugins<IRecordsListPlugin>().First().CreateInstance<IRecordsListPlugin>(context);
			RecordsListUc.Data = new List<IAudioBook>();
			StatusBarStateModel = new StatusBarStateViewModel(IsCanCancelAsyncOperation, CancelAsyncOperation);
			SiteParserModel = new SiteParserViewModel(this, context.PluginsFactory.GetPlugins<ISiteParserPlugin>().First().CreateInstance<ISiteParserPlugin>(context), () => { return SelectedItems; });
			RecordTargetDownloaderModel = new RecordTargetDownloaderViewModel(context, StatusBarStateModel, context.PluginsFactory.GetPlugins<IRecordTargetDownloaderPlugin>().First().CreateInstance<IRecordTargetDownloaderPlugin>(context), () => SelectedItems, () => Filter.BeginUpdateCacheAsync(UpdateTypeEnum.Loaded, (ex) => { if (ex != null) throw ex; }));
			Filter = context.PluginsFactory.GetPlugins<IFilteringLogicPlugin>().First().CreateInstance<IFilteringLogicPlugin>(context);
			Filter.PropertyChanged += Filter_PropertyChanged;
			Filter.BeginUpdateCacheAsync(UpdateTypeEnum.Hidden | UpdateTypeEnum.Loaded | UpdateTypeEnum.Values, ex => { });
			NormalizationSettingsEditorModel = new NormalizationSettingsEditorViewModel(context);
			GrouppingLogicModel = new GrouppingLogicViewModel(context.PluginsFactory.GetPlugins<IGrouppingLogicPlugin>().Select(item => item.CreateInstance<IGrouppingLogicPlugin>(context)),
				group => {
					var dbContainer = Context.CreateDbContainer(false);
					if (_getRecordsCancellationTokenSource != null) _getRecordsCancellationTokenSource.Cancel();
					_getRecordsCancellationTokenSource = new CancellationTokenSource();
					GetCurrentRecordsAsync(dbContainer, group, Filter, SetCurrentRecords, _getRecordsCancellationTokenSource.Token);
					OnPropertyChanged();
				});

			_hideSelectedRecordsCommand = new DelegateCommand(HideSelectedRecordsCommandExecute, HideSelectedRecordsCommandCanExecute);
			_refreshCommand = new DelegateCommand((parameter) => RefreshRecordsListData());
			_setReplacementCommand = new DelegateCommand(SetReplacement, IsCanSetReplacement);
			_showCachedInBrowserCommand = new DelegateCommand(ShowCachedInBrowserCommandExecute, ShowCachedInBrowserCommandCanExecute);
			_configCommand = new DelegateCommand((parameter) => Context.ShowConfigWindow(null));
		}

		#endregion
		#region + Logic +

		public void BrowserWindowPluginWindowPluginClosed(object sender, EventArgs e)
		{
			_browserWindowPlugin.Dispose();
			_browserWindowPlugin = null;
		}

		public void CancelAsyncOperation(object parameter)
		{
			RecordTargetDownloaderModel.CancelAsyncOperation();
			SiteParserModel.CancelAsyncOperation();
		}

		public bool IsCanCancelAsyncOperation(object parameter)
		{
			return true;
			//return RecordTargetDownloaderModel.IsAsyncOperationExecuting || SiteParserModel.IsAsyncOperationExecuting;
		}

		public void RefreshRecordsListData()
		{
			var dbContainer = Context.CreateDbContainer(false);
			GetCurrentRecordsAsync(dbContainer, GrouppingLogicModel.SelectedGroup, Filter, SetCurrentRecords, _getRecordsCancellationTokenSource.Token);
		}

		public void ShowInBrowser([NotNull] IAudioBook record)
		{
			SiteParserModel.SiteParserPlugin.BeginDownloadRecordSourcePageAsync(record.Key, (pageHtml, ex) =>
				{
					Application.Current.Dispatcher.CheckAccess(() =>
						{
							BrowserWindowPlugin.ShowRecordPage(record.Title, pageHtml);
							if (!BrowserWindowPlugin.IsDisplayed) _browserWindowPlugin.Display();
						});
				}, CancellationToken.None);
		}

		private static void GetCurrentRecordsAsync(IDbContainer dbContainer, Group currentGroup, IFilteringLogicPlugin filteringLogicPlugin, Action<IDbContainer, IEnumerable<IAudioBook>, CancellationToken> completedCallback, CancellationToken cancellationToken)
		{
			if (currentGroup == null) return;

			Task.Factory.StartNew(
				() =>
				{
					//Thread.Sleep(500);
					if (cancellationToken.IsCancellationRequested) return;

					currentGroup.BeginGetRecordsAsync(dbContainer, (grouppedRecords, ex) =>
						{
							if (ex != null) throw new Exception("Ошибка получения списка записей", ex);

							if (!cancellationToken.IsCancellationRequested)
							{
								if (filteringLogicPlugin.IsEnabled)
								{
									filteringLogicPlugin.BeginFilterAsync(grouppedRecords, (filterredRecords,ex1) =>
										{
											if (ex1 != null) throw new Exception("Ошибка фильтрации записей", ex);

											if (!cancellationToken.IsCancellationRequested) completedCallback(dbContainer, filterredRecords, cancellationToken);
										}, cancellationToken);
								}
								else
								{
									completedCallback(dbContainer, grouppedRecords, cancellationToken);
								}
							}
						}, cancellationToken);
				});
		}

		private void Filter_PropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			if (e.PropertyName == "IsOnUpdate") return;
			if (_getRecordsCancellationTokenSource != null) _getRecordsCancellationTokenSource.Cancel();
			_getRecordsCancellationTokenSource = new CancellationTokenSource();

			var dbContainer = Context.CreateDbContainer(false);
			GetCurrentRecordsAsync(dbContainer, GrouppingLogicModel.SelectedGroup, Filter, SetCurrentRecords, _getRecordsCancellationTokenSource.Token);
		}

		private bool HideSelectedRecordsCommandCanExecute(object parameter)
		{
			return SelectedItems.AnySafe();
		}

		private void HideSelectedRecordsCommandExecute(object parameter)
		{
			var selected = SelectedItems.ToDictionary(item => item.GroupKey + "\\" + item.Key, item => item);

			using (var dbContainer = Context.CreateDbContainer(true))
			{
				var groups = selected.Values.GroupBy(item => item.GroupKey);

				var existedHidden = new Dictionary<string, IHiddenRecord>();

				foreach (var group in groups)
				{
					foreach (var audioBook in dbContainer.HiddenRecordSet.GetHiddenRecords(group.Key, new HashSet<string>(group.Select(item => item.Key))))
					{
						existedHidden.Add(audioBook.RecordGroupKey + "\\" + audioBook.RecordKey, audioBook);
					}
				}

				//existedHidden.AddRange(dbContainer.HiddenRecordSet.GetHiddenRecords(group.Key, new HashSet<string>(group.Select(item => item.Key))));
				//var existedHidden = dbContainer.HiddenRecordSet.GetHiddenRecords(item => selected.Keys.Contains(item.RecordGroupKey + "\\" + item.RecordKey)).ToDictionary(item => item.RecordGroupKey + "\\" + item.RecordKey, item => item);

				var hiddenRecords = new List<IHiddenRecord>();

				foreach (var selectedItem in selected)
				{
					IHiddenRecord existed;
					if (!existedHidden.TryGetValue(selectedItem.Key, out existed))
					{
						var hiddenRecord = dbContainer.HiddenRecordSet.CreateHiddenRecord();
						hiddenRecord.RecordGroupKey = selectedItem.Value.GroupKey;
						hiddenRecord.RecordKey = selectedItem.Value.Key;
						hiddenRecords.Add(hiddenRecord);
					}
				}

				dbContainer.HiddenRecordSet.AddHiddenRecord(hiddenRecords.ToArray());

				dbContainer.SaveChanges();
				Filter.BeginUpdateCacheAsync(UpdateTypeEnum.Hidden, ex => { });
			}
		}

		private bool IsCanSetReplacement(object arg)
		{
			return SelectedItems.AnySafe();
		}

		[NotifyPropertyChangedInvocator]
		private void OnPropertyChanged([CallerMemberName] string propertyName = null)
		{
			PropertyChangedEventHandler handler = PropertyChanged;
			if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
		}

		private void SetCurrentRecords(IDbContainer dbContainer, IEnumerable<IAudioBook> records, CancellationToken cancellationToken)
		{
			if (!cancellationToken.IsCancellationRequested)
			{
				if (_recordsListUc.Dispatcher.CheckAccess())
				{
					records = records.ToArray();
					_recordsListUc.Data = records;
					if (_dbContainer4UI != null) _dbContainer4UI.Dispose();
					_dbContainer4UI = dbContainer;
				}
				else
				{
					_recordsListUc.Dispatcher.Invoke(() => SetCurrentRecords(dbContainer, records, cancellationToken));
				}
			}
		}

		private void SetReplacement(object obj)
		{
			NormalizationSettingsEditorModel.NormalizationSettingsEditorPlugin.TargetRecordsForEdit = SelectedItems.ToArray();
			NormalizationSettingsEditorModel.IsActive = true;
		}

		private bool ShowCachedInBrowserCommandCanExecute(object parameter)
		{
			return SelectedItems.AnySafe();
		}

		private void ShowCachedInBrowserCommandExecute(object parameter)
		{
			var first = SelectedItems.FirstOrDefault();
			if (first != null)
			{
				ShowInBrowser(first);
			}
		}

		private void _recordsListUc_ItemDoubleClick(object sender, ItemDoubleClickRowEventArgs e)
		{
			if (e.Target != null) ShowInBrowser(e.Target);
		}

		#endregion
	}
}